/*
    curlplusplus - a c++ wrapper around libcurl
    Copyright (C) 2012  Francesco "Franc[e]sco" Noferi (francesco1149@gmail.com)

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the
    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
    Boston, MA  02110-1301, USA.
*/

#pragma once

#include "multi.hpp"
#include <curl/curl.h>

namespace curlpp
{
    /**
     * Wraps libcurl's easy interface.
     */
    class easy
    {
        friend void multi::add_handle(easy *easy_handle) throw(const exception &);
        friend void multi::remove_handle(easy *easy_handle) throw(const exception &);
        friend easy *multi::get_handle(CURLMsg *msg) throw();

    public:
        /**
         * Default constructor.
         *
         * @throw curlpp::exception an exception that describes the general cause of the error.
         * @see easy(const easy &other)
         */
        easy() throw(const exception &);

        /**
         * Clone constructor.
         *
         * @param other the easy instance to be cloned.
         * @throw curlpp::exception an exception that describes the general cause of the error.
         * @see easy()
         */
        easy(const easy &other) throw(const exception &);

        /**
         * Default destructor.
         */
        virtual ~easy() throw();

        /**
         * Sets a cURL option.
         * See <a href="http://curl.haxx.se/libcurl/c/curl_easy_setopt.html">curl_easy_setopt</a> for more info
         * about the available options.
         *
         * @param tag the option to set.
         * @param value the value to assign to the option.
         *
         * @throw curlpp::exception an exception that describes the general cause of the error.
         *
         * @see reset()
         */
        template <typename T>
        void setopt(CURLoption tag, T value) throw(const exception &)
        {
            attempt(curl_easy_setopt(curl, tag, value), "easy.setopt: failed to set option - ");
        }

        /**
         * Resets all options of the cURL instance.
         * @see setopt()
         */
        void reset() throw();

        /**
         * Gets information of the cURL instance.
         * See <a href="http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html">curl_easy_getinfo</a> for more info
         * about the available information.
         *
         * @param info the information to obtain.
         * @param pdst a pointer to the variable that will store the retrieved information.
         *
         * @throw curlpp::exception an exception that describes the general cause of the error.
         *
         * @see setopt()
         */
        template <typename T>
        void getinfo(CURLINFO info, T pdst) throw(const exception &)
        {
            attempt(curl_easy_getinfo(curl, info, pdst), "easy.getinfo: failed to get info - ");
        }

        /**
         * Using this function, you can explicitly mark a running connection to get paused,
         * and you can unpause a connection that was previously paused.
         * For more info and the list of the available bitmasks, see
         * <a href="http://curl.haxx.se/libcurl/c/curl_easy_pause.html">curl_easy_pause</a>.
         *
         * @param bitmask The bitmask argument is a set of bits that sets the new state of the connection.
         * @throw curlpp::exception an exception that describes the general cause of the error.
         * @see reset()
         */
        void pause(int bitmask) throw(const exception &);

        /**
         * Sends raw data through the estabilished connection.
         * <a href="http://curl.haxx.se/libcurl/c/curl_easy_send.html">curl_easy_send</a>.
         *
         * @param buffer pointer to the data that will be sent.
         * @param buflen length of the data.
         * @param cb_sent pointer to a size_t variable that will store the total size of
         *                the sent data in bytes.
         *
         * @throw curlpp::exception an exception that describes the general cause of the error.
         *
         * @see setopt()
         * @see perform()
         * @see getinfo()
         * @see recv()
         */
        void send(void *buffer, size_t buflen, size_t *cb_sent) throw(const exception &);

        /**
         * Recieves raw data from the estabilished connection.
         * <a href="http://curl.haxx.se/libcurl/c/curl_easy_recv.html">curl_easy_recv</a>.
         *
         * @param buffer pointer to the buffer that will store the recieved data.
         * @param buflen maximum length of the buffer.
         * @param cb_received pointer to a size_t variable that will store the total size of
         *                    the recieved data in bytes.
         *
         * @throw curlpp::exception an exception that describes the general cause of the error.
         *
         * @see setopt()
         * @see perform()
         * @see getinfo()
         * @see send()
         */
        void recv(void *buffer, size_t buflen, size_t *cb_received) throw(const exception &);

        /**
         * Performs the cURL request with the previously set options.
         * You can call perform as many times as you want from the same instance for multiple requests.
         * Note that this is not thread safe, and cannot be called multiple times simultaneously.
         *
         * @throw curlpp::exception an exception that describes the general cause of the error.
         * @see setopt()
         */
        void perform() throw(const exception &);

        /**
         * URL encodes the given string.
         * The returned string is dynamically allocated, therefore it must be deallocated
         * using curlpp::free().
         *
         * @param url the text to encode
         * @param length the length of the given string. If zero, the length will be automatically
         *               determined using strlen().
         * @return a pointer to the encoded url string or NULL if it failed.
         * @see unescape()
         * @see curlpp::free()
         */
        char *escape(char *url, int length);

        /**
         * URL decodes the given string.
         * The returned string is dynamically allocated, therefore it must be deallocated
         * using curlpp::free().
         *
         * @param url the text to encode
         * @param length the length of the given string. If zero, the length will be automatically
         *               determined using strlen().
         * @param outlength if non-NULL, the length of the returned string will be written to
         *                  the integer it points to. Useful when dealing with escaped strings
         *                  containing %00.
         * @return a pointer to the decoded url string or NULL if it failed.
         * @see escape()
         * @see curlpp::free()
         */
        char *unescape(char *url, int length, int *outlength = NULL);

    protected:
        CURL *curl; /**< Internal cURL easy handle */

        /**
         * If the passed CURLcode represents an error, throws an exception that describes
         * the error, otherwise does nothing.
         *
         * @param res the error code returned by a curl_easy function.
         * @param errortext a general description of the error. The curl error string will
         *                  be appended to this string.
         * @throw curlpp::exception an exception that describes the general cause of the error.
         */
        void attempt(CURLcode res, std::string errortext) throw(const exception &);
    };
}
